﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using TranslatorService;

namespace AzureTranslator
{
	// Get Client Id and Client Secret from https://datamarket.azure.com/developer/applications/
#pragma warning disable 1998
	public class AzureTranslator : ITranslatorService
	{
		AdmAuthentication admAuth;
		private const string baseUrl = "http://api.microsofttranslator.com/v2/Http.svc/";

		public TranslatorService.TranslatorServiceCapabilities ServiceCapabilities
		{
			get
			{
				return TranslatorService.TranslatorServiceCapabilities.DefinesWords |
					TranslatorService.TranslatorServiceCapabilities.DetectsLanguage |
					TranslatorService.TranslatorServiceCapabilities.HtmlText |
					TranslatorService.TranslatorServiceCapabilities.ProvidesAudio |
					TranslatorService.TranslatorServiceCapabilities.TranslatesPhrases |
					TranslatorService.TranslatorServiceCapabilities.UserSubmissions |
					TranslatorService.TranslatorServiceCapabilities.PlainText |
					TranslatorService.TranslatorServiceCapabilities.TranslatesWords |
					TranslatorService.TranslatorServiceCapabilities.RequiresAuthentication;
			}
		}

		public string ServiceName
		{
			get { return "Microsoft Translator"; }
		}

		public Uri ServiceURI
		{
			get { return new Uri("http://msdn.microsoft.com/en-us/library/dd576287.aspx"); }
		}

		public string[] AuthenticationParameterDetail
		{
			get { return new string[] { "ClientID", "ClientSecret" }; }
		}

		public async Task<bool> AuthenticateAsync(params string[] options)
		{
			if (options.Length != 2)
				throw new ArgumentException();
			admAuth = new AdmAuthentication(options[0], options[1]);
			try
			{
				await admAuth.GetAccessToken();
				return true;
			}
			catch { }
			return false;
		}

		public async Task<Dictionary<string, string>> GetSupportedLanguagesAsync(string locale = "en")
		{
			List<string> codes = await CallMethodAsync<List<string>>("GetLanguagesForTranslate");
			return await GetLanguageNamesMethod(codes.ToArray(), locale);
		}

		private async Task<Dictionary<string, string>> GetLanguageNamesMethod(string[] languageCodes, string locale)
		{
			Dictionary<string, string> output = new Dictionary<string, string>();
			string uri = HttpUtility.BuildUrl(baseUrl, "GetLanguageNames", "locale", locale, "appId", await admAuth.GetAuthToken());
			// create the request
			HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
			//request.Headers.Add("Authorization", await admAuth.GetAuthToken());
			request.ContentType = "text/xml";
			request.Method = "POST";
			System.Runtime.Serialization.DataContractSerializer dcs = new System.Runtime.Serialization.DataContractSerializer(Type.GetType("System.String[]"));
			using (System.IO.Stream stream = await request.GetRequestStreamAsync())
			{
				dcs.WriteObject(stream, languageCodes);
			}
			try
			{
				using (WebResponse response = await request.GetResponseAsync())
				{
					using (Stream stream = response.GetResponseStream())
					{
						string[] languageNames = (string[])dcs.ReadObject(stream);
						for (int i = 0; i < languageNames.Length; i++)
							output.Add(languageCodes[i], languageNames[i]);
					}
				}
			}
			catch
			{
			}
			return output;
		}

		public async Task<string> TranslateAsync(string sourceLang, string destLang, string value, InputFormat format = InputFormat.Text)
		{
			List<string> parameters = new List<string>();
			parameters.AddRange(new string[] { "text", value, "to", destLang, "contentType", format == InputFormat.Text ? "text/plain" : "text/html" });
			if (!string.IsNullOrWhiteSpace(sourceLang))
				parameters.AddRange(new string[] { "from", sourceLang });
			return await CallMethodAsync<string>("Translate", parameters.ToArray());
		}

		public async Task<string> DetectLanguageAsync(string value)
		{
			return await CallMethodAsync<string>("Detect", "text", value);
		}

		public async Task<Dictionary<string, string>> GetSupportedSpeakLanguagesAsync(string locale = "en")
		{
			List<string> codes = await CallMethodAsync<List<string>>("GetLanguagesForSpeak");
			return await GetLanguageNamesMethod(codes.ToArray(), locale);
		}

		public async Task<Stream> SpeakAsync(string sourceLang, string value, string format = null, string options = null)
		{
			List<string> parameters = new List<string>();
			parameters.AddRange(new string[] { "text", value, "language", sourceLang });
			if (!string.IsNullOrWhiteSpace(format))
				parameters.AddRange(new string[] { "format", format});
			if (!string.IsNullOrWhiteSpace(options))
				parameters.AddRange(new string[] { "options", options });
			return await CallMethodAsync<Stream>("Speak", parameters.ToArray());
		}

		public async Task<T> CallMethodAsync<T>(string methodName, params string[] parameters) where T : class
		{
			if (admAuth == null)
				throw new UnauthorizedAccessException();

			return await TranslatorService.HttpUtility.CallMethodAsync<T>(baseUrl, methodName, await admAuth.GetAuthToken(), parameters);
		}
	}

	[DataContract]
	internal class AdmAccessToken
	{
		[DataMember]
		public string access_token { get; set; }
		[DataMember]
		public string token_type { get; set; }
		[DataMember]
		public string expires_in { get; set; }
		[DataMember]
		public string scope { get; set; }
	}

	internal class AdmAuthentication
	{
		public static readonly string DatamarketAccessUri = "https://datamarket.accesscontrol.windows.net/v2/OAuth2-13";
		private string clientId;
		private string clientSecret;
		private string request;
		private AdmAccessToken token;
		private Timer accessTokenRenewer;

		//Access token expires every 10 minutes. Renew it every 9 minutes only.
		private const int RefreshTokenDuration = 9;

		public AdmAuthentication(string clientId, string clientSecret)
		{
			this.clientId = clientId;
			this.clientSecret = clientSecret;
			//If clientid or client secret has special characters, encode before sending request
			this.request = string.Format("grant_type=client_credentials&client_id={0}&client_secret={1}&scope=http://api.microsofttranslator.com", HttpUtility.UrlEncode(clientId), HttpUtility.UrlEncode(clientSecret));
			//renew the token every specfied minutes
			accessTokenRenewer = new Timer(new TimerCallback(OnTokenExpiredCallback), this, TimeSpan.FromMilliseconds(-1), TimeSpan.FromMilliseconds(-1));
		}

		public async Task<string> GetAuthToken()
		{
			AdmAccessToken tempToken = await GetAccessToken();
			return "Bearer" + " " + tempToken.access_token;
		}

		public async Task<AdmAccessToken> GetAccessToken()
		{
			if (this.token == null)
				await RenewAccessToken();
			return this.token;
		}

		private async Task RenewAccessToken()
		{
			this.token = await HttpPost(DatamarketAccessUri, this.request);
			try
			{
				accessTokenRenewer.Change(TimeSpan.FromMinutes(RefreshTokenDuration), TimeSpan.FromMilliseconds(-1));
			}
			catch (Exception ex)
			{
				System.Diagnostics.Debug.WriteLine(string.Format("Failed to reschedule the timer to renew access token. Details: {0}", ex.Message));
			}
			System.Diagnostics.Debug.WriteLine(string.Format("Renewed token for user: {0} is: {1}", this.clientId, this.token.access_token));
		}

		private async void OnTokenExpiredCallback(object stateInfo)
		{
			this.token = null;
		}

		private async Task<AdmAccessToken> HttpPost(string DatamarketAccessUri, string requestDetails)
		{
			//Prepare OAuth request 
			HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(DatamarketAccessUri);
			webRequest.ContentType = "application/x-www-form-urlencoded";
			webRequest.Method = "POST";
			byte[] bytes = Encoding.UTF8.GetBytes(requestDetails);
			//webRequest.ContentLength = bytes.Length;
			using (Stream outputStream = await webRequest.GetRequestStreamAsync())
			{
				outputStream.Write(bytes, 0, bytes.Length);
			}
			using (WebResponse webResponse = await webRequest.GetResponseAsync())
			{
				DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(AdmAccessToken));
				//Get deserialized object from JSON stream
				AdmAccessToken token = (AdmAccessToken)serializer.ReadObject(webResponse.GetResponseStream());
				return token;
			}
		}
	}
}
